-- 2022-8-15

--[[ 17.3 子模块和包

    Lua支持具有层次结构的模块名 通过点来分隔名称中的层次
    例如 一个名为mod.sub的模块是模块mod的一个子模块(submodule) 一个包(package)是一课由模块组成的完整的树 它是Lua语言中用于发行程序的单位

    当加载一个名为mod.sub的模块时 函数require依次使用原始的模块名"mod.sub"作为键来查询表package.loaded和表package.preload
    这里 模块名中的点像模块名中的其他字符一样 没有特殊含义

    然而 当搜索一个定义子模块的文件时 函数require会将点转换为另一个字符
    通常就是操作系统的目录分隔符(例如 POSIX操作系统的斜杠或Windows操作系统的放斜杠)
    转换之后 函数require会像搜索其他名称一样搜索这个名称 假如 假设目录分隔符是斜杠并且有如下路径:
    ./?.lua;/usr/local/lua/?.lua;/usr/local/lua/?/iniy.lua
    调用require "a.b"会尝试打开一下文件
        ./a/b.lua
        /usr/local/lua.a.b.lua
        /usr/local/lua/a/b/init.lua
    这种行为使得一个包中所有模块能够放到一个目录中
    例如 一个具有模块p p.a和p.b的包对应的文件可以分别是p/init.lua p/a.lua和p/b.lua 目录p又位于其他合适的目录中

    Lua语言使用的目录分隔符是编译时配置的 可以是任意字符串(请记住 Lua并不知道目录的存在)
    例如 没有目录层次的系统可以使用下划线作为"目标分隔符" 因此调用require "a.b"会搜索文件a_b.lua

    C语言中的名称不能包含点 因此一个用C语言编写的子模块a.b无法导出函数lua-open_a.b
    这时 函数require会将点转换为其他字符 即下划线 因此 一个名为a.b的C标准库应将其加载函数命名为luaopen_a_b

    作为一种额外的机制 函数require在加载C语言编写的子模块时还要另外一个搜索器 
    当函数找不到子模块对应的Lua文件或C文件时 它会再次搜索C文件所在的路径 不过这次将搜索包的名称
    例如 如果一个程序要加载子模块a.b.c 搜索器会搜索文件a
    如果找到了C标准库a 那么函数require就会在该库中搜索对应的加载函数luaopen_a_b_c
    这种机制允许一个发行包将几个子模块组织为一个C标准库 每个子模块有各自的加载函数

    从Lua语言的视角看 同一个包中的子模块没有显式的关联
    加载一个模块并不会自动加载任何子模块 同样 加载子模块也不会自动的加载其父模块
    当然 只要包的实现者愿意 也可以创造这种关联
    例如 一个特定的模块可能一开始就显式的加载它的一个或全部子模块

    -- 补充 使用require来加载模块时 主要文件和终端的位置 终端只能在子文件中查找 要找的文件必须返回对应的表
]]